class ImportStuff < Controller

  def index e
    doc = ''
    if e.request.post?
      require 'appengine-apis/urlfetch'

      if e.params['import-templates']
        (import_templates e)
      else
        doc << (import_everything e)
      end
    end

    <<HTML
    <h1>Import</h1>
    <form method="POST">
      From
      <input
        type="text"
        name="import_from"
        value="#{e.params['import_from'] || 'http://localhost:81'}"
      />
      <br />
      <input type="submit" name="import-everything" value="Import everything"/>
      
      <input type="submit" name="import-templates" value="Import templates"/>
    </form>
    #{doc}
HTML
  end

  def import_templates e
    resource = e.params['import_from']
    i = ImportExport::Import.new(resource)
    i.merge(i.select :kind => ContentTemplate.kind)
  end

  def import_everything e
    resource = e.params['import_from']
    if e.params['identities']
      ImportExport::Import.fetch(
        resource, e.params['identities'].split('-')
      ).each { |r|
         ImportExport::Import.import_thing Thing.from_hash(r)
      }
      # cssmanager might got some new info, so reset the singleton
      CssManager.reset
    else
      resources = (ImportExport::Import.new(resource).list_not_same)
      doc = ''
      doc <<
      '<form method="post">
        <input
          type="hidden"
          name="identities"
          value="'+resources.join("-")+'" />
        <input type="hidden" name="import_from" value="'+resource+'">
        <input type="submit" value="merge 20"/>
      </form>'

      doc << resources.inspect
    end
  end
end



module ImportExport

  class Import
    def self.unserialize data
      Thing.from_json data
    end

    def self.fetch resource_url, identities
      doc = AppEngine::URLFetch.fetch(resource_url+'/export/dump',
          :payload => (Rack::Utils.build_query 'identities' => identities.join("-")),
          :method => 'POST'
        )
      data = JSON.parse(doc.body)
      #p :data, data
      data
    end

    def self.import data
      import_thing(Thing.from_json data)
    end

    def self.import_thing thing
      # this will be very nice once we start keeping older versions around
      if thing.save
        Thing.all({
            :identity => thing.identity,
            :version.not => thing.version
          }).
          each { |c| c.destroy }
      end
      thing
    end

    attr_accessor :resource_url

    def initialize url
      @resource_url = url
    end

    def remote_things
      @r ||= JSON.parse AppEngine::URLFetch::HTTP.
        get(URI.parse(resource_url)+'/export/list')
    end

    def get_remote_things url
      JSON.parse AppEngine::URLFetch::HTTP.get(url)
    end

    def select filter
      get_remote_things(URI.parse(resource_url).merge(
          '/export/select?'+
          Rack::Utils.build_query(:filter => filter.to_json))
      )
    end

    def merge things
      compare(things).each { |local, remote|
        local.destroy if local
        remote.save
      }
    end

    def compare things
      things = Hash[*
          things.inject([]) { |a, t| a.push(t.identity, [nil, t]) }
        ]
      Thing.all(:identity => things.keys).
        each { |local|
          things[local.identity][0] = local
        }
      things.values # [local, remote] pairs
    end

    def list_not_same limit = 20
      resources = []

      f_resources = remote_things

      while resources.length < limit && f_resources.length > 0
        remote_identities = f_resources.slice!(0, limit)
        found = []
        different = (select_different_versions limit, remote_identities, found)
        resources += different
        resources.push(*remote_identities.map { |r| r[0] } - found) #.map(&:identities))
      end
      resources
    end

    def select_different_versions limit, chunk, found
      Thing.all(
          :identity => chunk.map { |r| r[0] },
          :limit => limit
        ).select { |thing|
          found << thing.identity
            thing.version != chunk.select { |r| r[0] == thing.identity }.first[1]
          }.map { |t| t.identity }
    end


    
    class Mock < Import
      attr_accessor :things, :export, :remote_things, :used_urls

      def remote_things
        if @remote_things
          @remote_things.map { |r| 
            [r.identity,  r.version]
          }
        else
          super
        end
      end

      def get_remote_things url
        (@used_urls ||= []) << url
        ['mocked']
      end


      def list url
        @r ||= export.things.map { |t| 
          [t.identity, t.version]
        }
      end

      def select_different_versions limit, chunk, found
        if !@things
          return super
        end
        identities = chunk.map { |r| r[0] }
        things.select { |t|
            identities.include? t.identity
          }.select { |thing|
            found << thing.identity
              thing.version != chunk.select { |r|
                r[0] == thing.identity }.first[1]
            }.map { |t| t.identity }
      end
    end
    

    class Test < FwTest
      def test_list_not_same
        i = Mock.new("no url")
        i.remote_things = [Thing.new, Thing.new]
        DataMapper.setup(:testing, :adapter => 'in_memory')
        repository(:testing) {
          eq i.list_not_same, i.remote_things.map { |t| t[0] }
        }
      end

      def test_serialization
        t1 = Thing.new
        t2 = Import.unserialize Export.serialize t1

        eq t1.id, t2.id
        eq t1.version, t2.version
        eq t1.previous_version, t2.previous_version
        eq t1.identity, t2.identity
      end

      def test_import_persists_imported_thing_as_it_came
        t1 = Thing.new
        data = Export.serialize t1

        DataMapper.setup(:test, :adapter => 'in_memory')
        repository(:test) {
          Import.import data

          t2 = Thing.first(:identity => t1.identity)
          eq t2.identity, t1.identity, "identities do not match"
          eq t2.version, t1.version, "versions do not matach"
          eq t2.previous_version, t1.previous_version
        }
      end

      def test_import_overwrites_existing_record_if_present
        DataMapper.setup(:test, :adapter => 'in_memory')
        repository(:test) {
          local_copy = Thing.new
          local_copy.save

          local_copy.version = "NEW"
          Import.import(Export.serialize local_copy)

          eq Thing.all(:identity => local_copy.identity).length, 1
        }
      end

      def test_list_not_same_includes_things_with_different_version
        DataMapper.setup(:test, :adapter => 'in_memory')
        repository(:test) {
          i = Mock.new("")
          i.remote_things = [Thing.new(:identity => 'i1', :version => 'v1')]
          i.things = [Thing.new(:identity => 'i1', :version => 'v2')]
          eq i.list_not_same, ["i1"]
        }
      end

      def test_list_not_same_includes_things_with_unknown_identities
        DataMapper.setup(:test, :adapter => 'in_memory')
        repository(:test) {
          i = Mock.new("")
          i.remote_things = [Thing.new(:identity => 'i1', :version => 'v1')]
          eq i.list_not_same, ["i1"]
        }
      end

      def test_list_not_same_does_not_include_things_with_same_version
        DataMapper.setup(:test, :adapter => 'in_memory')
        repository(:test) {
          i = Mock.new("")
          i.remote_things = [Thing.new(:identity => 'i1', :version => 'v1')]
          i.things = [Thing.new(:identity => 'i1', :version => 'v1')]
          eq i.list_not_same, []
        }
      end

      def test_importing_keeps_version_identity_and_changed_at
        DataMapper.setup(:test, :adapter => 'in_memory')
        repository(:test) {
          t1 = Thing.new
          t1.identity = "item1"
          t1.version = "version1"

          t1.to_json
          ImportExport::Import.import(t1.to_json)

          eq Thing.first().identity, "item1"
        }
      end

      def test_import_removes_only_identity_with_different_version
        DataMapper.setup(:test, :adapter => 'in_memory')
        repository(:test) {
          t1 = Thing.new
          t1.identity = "item1"
          t1.version = "version1"
          t1.save

          t2 = t1.clone
          t2.version = "version2"
          ImportExport::Import.import(t2.to_json)

          eq Thing.all().count, 1
          eq Thing.first().version, "version2"
        }
      end

      def test_select_from_remote_source_url_making
        import = Mock.new("http://example.org")
        import.select :identity => '123'
        query = Rack::Utils.build_query(:filter => {:identity => '123'}.to_json)
        eq import.used_urls.map(&:to_s), ["http://example.org/export/select?"+query]
      end

      def test_merge_overrides_different_things
        DataMapper.setup(:test, :adapter => 'in_memory')
        repository(:test) {
          t1 = Thing.new(:identity => 'abba', :version => "version1")
          t1.save
        
          t2 = Thing.new(:identity => 'abba', :version => "version2")
          Import.new("").merge([t2])
          eq Thing.all.to_a.map(&:version), ["version2"]
        }
      end

      def test_merge_adds_different_things
        DataMapper.setup(:test, :adapter => 'in_memory')
        repository(:test) {
          merge = [Thing.new(:identity => 'abba')]
          Import.new("").merge(merge)
          eq Thing.all.to_a, merge
        }
      end
    end


    class IntegrationTest < FwTest
      def test_import_of_few_things
        DataMapper.setup(:testing, :adapter => 'in_memory')
        repository(:testing) {
          import = Mock.new ""

          things = (0..4).to_a.map {
              Thing.new.tap(&:save)
            }
          import.remote_things = things

          import.things = [things[1], things[3]]

          eq import.list_not_same(), [
            things[0].identity,
            things[2].identity,
            things[4].identity
          ]
        }
      end

      def test_select_from_remote_source_integration
        DataMapper.setup(:testing, :adapter => 'in_memory')
        repository(:testing) {
          things = (0..2).to_a.map {
            Thing.new.tap &:save
          }

          import = Mock.new("http://example.org")
          import.select :identity => things[1].identity

          export = ExportController.new.select(Event.new(nil,
            :params => Rack::Utils.parse_query(import.used_urls[0].query)
          ))

          exported = JSON.parse(export)
          eq exported.length, 1
          eq exported[0]['identity'], things[1].identity
        }
      end
    end

  end
end